package openjdk.stream;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * https://www.e-learn.cn/content/java/784232
 */
import static java.util.stream.Collectors.*;

public class GrpSumTwoField {
    private static class Foo {
        public int id, targetCost, actualCost;
        public String ref;

        public Foo(int id, String ref, int targetCost, int actualCost) {
            this.id = id;
            this.targetCost = targetCost;
            this.actualCost = actualCost;
            this.ref = ref;
        }

        @Override
        public String toString() {
            return String.format("Foo(%d,%s,%d,%d)", id, ref, targetCost, actualCost);
        }
    }

    public static void main(String[] args) {
        List<Foo> list = Arrays.asList(
                new Foo(1, "P1", 300, 400),
                new Foo(2, "P2", 600, 400),
                new Foo(3, "P3", 30, 20),
                new Foo(3, "P3", 70, 20),
                new Foo(1, "P1", 360, 40),
                new Foo(4, "P4", 320, 200),
                new Foo(4, "P4", 500, 900));


        // counting.
        list.stream()
                .collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
                .forEach((id, count) -> System.out.println(id + "\t" + count));


        // Summing up one property:
        list.stream()
                .collect(Collectors.groupingBy(foo -> foo.id,
                        Collectors.summingInt(foo -> foo.targetCost)))
                .forEach((id, sumTargetCost) -> System.out.println(id + "\t" + sumTargetCost));


        // group by id
        List<Foo> transform = list.stream()
                .collect(Collectors.groupingBy(foo -> foo.id))
                .entrySet().stream()
                .map(e -> e.getValue().stream()
                        .reduce((f1, f2) -> new Foo(f1.id, f1.ref, f1.targetCost + f2.targetCost, f1.actualCost + f2.actualCost)))
                .map(f -> f.get())
                .collect(Collectors.toList());
        System.out.println(transform);


        // group by id 2
        System.out.println(">>>>>>>>>>>>> grp by id 2 >>>>>");
        list.stream().collect(groupingBy(foo -> foo.id, collectingAndThen(reducing(
                (a, b) -> new Foo(a.id, a.ref, a.targetCost + b.targetCost, a.actualCost + b.actualCost)),
                Optional::get)))
                .forEach((id, foo) -> System.out.println(foo));


        /**
         *  By using the three-arg form of groupingBy we can specify a supplier for the actual Map implementation
         *  which will determine the key equality. By using a sorted map with a comparator comparing multiple
         *  properties we get the desired behavior without the need for an additional class.
         *  We only have to take care not to use properties from the key instances our comparator ignored,
         *  as they will have just arbitrary values:
         */
        list.stream().collect(groupingBy(Function.identity(),
                () -> new TreeMap<>(
                        // we are effectively grouping by [id, actualCost]
                        Comparator.<Foo, Integer>comparing(foo -> foo.id).thenComparing(foo -> foo.actualCost)
                ), // and aggregating/ summing targetCost
                Collectors.summingInt(foo -> foo.targetCost)))
                .forEach((group, targetCostSum) ->
                        // take the id and actualCost from the group and actualCost from aggregation
                        System.out.println(group.id + "\t" + group.actualCost + "\t" + targetCostSum));
    }
}